Passed
Push — dev ( 86a962...d312fb )
by Salim
03:41
created

DicomUploadModel.getInstance   A

Complexity

Conditions 5

Size

Total Lines 12
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 10
dl 0
loc 12
c 0
b 0
f 0
cc 5
rs 9.3333

2 Functions

Rating   Name   Duplication   Size   Complexity  
A DicomUploadModel.isKnownSerie 0 3 1
A DicomUploadModel.isKnownInstance 0 3 1
1
/**
2
 Copyright (C) 2018-2020 KANOUN Salim
3
 This program is free software; you can redistribute it and/or modify
4
 it under the terms of the Affero GNU General Public v.3 License as published by
5
 the Free Software Foundation;
6
 This program is distributed in the hope that it will be useful,
7
 but WITHOUT ANY WARRANTY; without even the implied warranty of
8
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9
 Affero GNU General Public Public for more details.
10
 You should have received a copy of the Affero GNU General Public Public along
11
 with this program; if not, write to the Free Software Foundation, Inc.,
12
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
13
 */
14
15
class DicomUploadModel {
16
	constructor(config) {
17
		this.config = config;
18
19
		// Drop zone object
20
		this.dz = new DicomDropZone('#du-drop-zone');
0 ignored issues
show
Bug introduced by
The variable DicomDropZone seems to be never declared. If this is a global, consider adding a /** global: DicomDropZone */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
21
22
		// Files dropped in the drop zone
23
		this.loadedFiles = [];
24
25
		// Files waiting for processing (and decompressed zip content)
26
		this.queuedFiles = [];
27
28
		// Files that just have been successfully parsed
29
		this.parsedFiles = [];
30
31
		// Files that could not have been recognized as valid dicom file
32
		this.ignoredFiles = [];
33
34
		// ~
35
36
		// Studies list
37
		this.studies = [];
38
39
40
		// Studies which passed the checks successfully
41
		this.validStudies = [];
42
43
		// Studies which do not have critical warnings and
44
		// have both rejected series and valid series ready to upload
45
		this.incompleteStudies = [];
46
47
		// Studies which did not passed the checks
48
		this.rejectedStudies = [];
49
50
51
		// Files waiting for upload
52
		this.queuedStudies = [];
53
54
		// ~
55
56
		this.expectedVisits = [];
57
58
		// ~
59
	}
60
61
	getStudy(studyInstanceUID) {
62
		for (let st of this.studies) {
63
			if (st.studyInstanceUID == studyInstanceUID) {
64
				return st;
65
			}
66
		}
67
		return null;
68
	}
69
70
	getSerie(seriesInstanceUID) {
71
		for (let st of this.studies) {
72
			for (let sr of st.series) {
73
				if (sr.seriesInstanceUID == seriesInstanceUID) {
74
					return sr;
75
				}
76
			}
77
		}
78
		return null;
79
	}
80
81
	isKnownStudy(dicomFile) {
82
		return this.getStudy(dicomFile.getStudyInstanceUID()) !== null;
83
	}
84
85
	isKnownSerie(dicomFile) {
86
		return this.getSerie(dicomFile.getSeriesInstanceUID()) !== null;
87
	}
88
89
	isKnownInstance(dicomFile) {
90
		return this.getInstance(dicomFile.getSOPInstanceUID()) !== null;
91
	}
92
93
	// ~
94
95
	queueStudy(study) {
96
		study.isQueued = true;
97
		this.put(study, 'queuedStudies');
98
	}
99
100
	dequeueStudy(study) {
101
		study.isQueued = false;
102
		this.remove(study, 'queuedStudies');
103
		study.dequeueAllSeries();
104
	}
105
106
107
	setStatusStudy(study, status) {
108
		study.status = status;
109
		this.remove(study, 'incompleteStudies');
110
		this.remove(study, 'rejectedStudies');
111
		this.remove(study, 'validStudies');
112
		switch (status) {
113
			case 'incomplete':
114
				this.put(study, 'incompleteStudies');
115
				break;
116
			case 'rejected':
117
				this.put(study, 'rejectedStudies');
118
				break;
119
			case 'valid':
120
				this.put(study, 'validStudies');
121
				break;
122
			default:
123
				//console.warn('Invalid Study State');
124
		}
125
	}
126
127
	hasQueuedStudies() {
128
		for (let st of this.queuedStudies) {
129
			if (st.hasQueuedSeries()) {
130
				return true;
131
			}
132
		}
133
		return false;
134
	}
135
136
	// ~
137
138
	/**
139
   * Delete specific element from a given array
140
   */
141
	remove(elmt, fromArrName) {
142
		const index = this[fromArrName].indexOf(elmt);
143
		if (index > -1) {
144
			this[fromArrName].splice(index, 1);
145
		}
146
	}
147
148
	/**
149
	 * Move specific element from a given array to another
150
	 */
151
	move(elmt, fromArrName, toArrName) {
152
		const index = this[fromArrName].indexOf(elmt);
153
		if (index > -1) {
154
			this[fromArrName].splice(index, 1);
155
			this[toArrName].push(elmt);
156
		}
157
	}
158
159
	/**
160
	 * Push an element to an array
161
	 */
162
	put(elmt, toArrName) {
163
		if (!this[toArrName].includes(elmt)) {
164
			this[toArrName].push(elmt);
165
		}
166
	}
167
168
	/**
169
	* Register the study, serie, instance of a dicom file if not known yet
170
	* @throws 'Secondary Capture Image Storage are not allowed.'
171
	* @throws 'Not expected visit.'
172
	* @throws 'DICOM file duplicates found'
173
	*/
174
	register(dicomFile) {
175
176
		// Check SOP Class UID is not Secondary Capture Image Storage
177
		if (dicomFile.isSecondaryCaptureImg()) {
178
			throw 'Secondary Capture Image Storage are not allowed.';
179
		}
180
181
		if(dicomFile.isDicomDir()){
182
			throw 'Dicomdir File, ignoring'
183
		}
184
185
		// Check if the study has already been registered (on client-side)
186
		if (!this.isKnownStudy(dicomFile)) {
187
			let st = new Study(
0 ignored issues
show
Bug introduced by
The variable Study seems to be never declared. If this is a global, consider adding a /** global: Study */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
188
				dicomFile.getStudyInstanceUID(),
189
				dicomFile.getStudyDate(),
190
				dicomFile.getStudyDescription(),
191
				dicomFile.getStudyID(),
192
				dicomFile.getAccessionNumber(),
193
				dicomFile.getAcquisitionDate(),
194
				dicomFile.getPatientID(),
195
				dicomFile.getPatientName(),
196
				dicomFile.getPatientBirthDate(),
197
				dicomFile.getPatientSex()
198
			);
199
200
			this.studies.push(st);
201
202
			// Check if the study is not already registered in the server
203
			if (!DAO.fetchIsNewStudy(this.config, st.getOrthancID(), (isNew) => {
0 ignored issues
show
Bug introduced by
The variable DAO seems to be never declared. If this is a global, consider adding a /** global: DAO */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
204
				if (isNew == 'false') {
205
					st.setWarning('isNotNewStudy', 'This study is already known by the server.');
206
				}
207
			}));
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
208
		}
209
210
		// Check if the series has already been registered
211
		if (!this.isKnownSerie(dicomFile)) {
212
			let study = this.getStudy(dicomFile.getStudyInstanceUID());
213
			study.series.push(new Serie(
0 ignored issues
show
Bug introduced by
The variable Serie seems to be never declared. If this is a global, consider adding a /** global: Serie */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
214
				dicomFile.getSeriesInstanceUID(),
215
				dicomFile.getSeriesNumber(),
216
				dicomFile.getSeriesDate(),
217
				dicomFile.getSeriesDescription(),
218
				dicomFile.getModality()
219
			));
220
		}
221
222
		// Check if the instance has already been registered
223
		if (!this.isKnownInstance(dicomFile)) {
224
			const inst = new Instance(
0 ignored issues
show
Bug introduced by
The variable Instance seems to be never declared. If this is a global, consider adding a /** global: Instance */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
225
				dicomFile.getSOPInstanceUID(),
226
				dicomFile.getInstanceNumber(),
227
				dicomFile
228
			);
229
230
			inst.dicomFile.anonymise();
231
232
			let serie = this.getSerie(dicomFile.getSeriesInstanceUID());
233
			serie.instances.push(inst);
234
235
		} else {
236
			throw 'Duplicates. This instance is already loaded.';
237
		}
238
	}
239
240
	checkStudies() {
241
		for (let st of this.studies) {
242
243
			// Check if the study corresponds to the visits in wait for series upload
244
			let expectedVisit = this.findExpectedVisit(st);
245
			if (expectedVisit === undefined) {
246
				st.setWarning('notExpectedVisit', 'You should check/select the patient. The imported study informations do not match with the expected ones.', true, false, true);
247
			} else {
248
				delete st.warnings['notExpectedVisit'];
249
				if (!this.config.multiImportMode) {
250
					st.visit = expectedVisit;
251
				}
252
			}
253
254
			// Check if visit ID is set
255
			if (st.visit == null || typeof st.visit.idVisit === undefined) {
0 ignored issues
show
Best Practice introduced by
Comparing st.visit to null using the == operator is not safe. Consider using === instead.
Loading history...
256
				st.setWarning('visitID', 'You should check/select the patient. Null visit ID.', false, true, false);
257
			} else {
258
				delete st.warnings['visitID'];
259
			}
260
261
			// Check inner series
262
			this.checkSeries(st);
263
264
			// Check if study has warnings
265
			if (st.hasWarnings()) {
266
				if (!st.hasCriticalWarnings() && st.hasValidSeries()) {
267
					this.setStatusStudy(st, 'incomplete');
268
				} else {
269
					this.setStatusStudy(st, 'rejected');
270
					this.dequeueStudy(st);
271
				}
272
			} else {
273
					this.setStatusStudy(st, 'valid');
274
			}
275
276
		}
277
	}
278
279
	checkSeries(st) {
280
		function isset(e) {
281
			return !(e == 'null' || e === undefined || e == '');
282
		}
283
284
		for (let sr of st.series) {
285
286
			let dicomFile = sr.instances[0].dicomFile;
287
288
			// Check missing tags
289
			if (!isset(dicomFile.getModality())) {
290
				sr.setWarning('missingTag00080060', 'Missing tag: Modality', true);
291
			} else {
292
				if (!isset(dicomFile.getDicomTag('00080021')) && !isset(dicomFile.getDicomTag('00080022')) ) {
293
					sr.setWarning('missingTag00080022', 'Missing tag: SeriesDate', true);
294
				}
295
				if (sr.modality == 'PT') {
296
					if ( !isset(dicomFile.getDicomTag('00101030')) ) {
297
						sr.setWarning('missingTag00101030', 'Missing tag: Patient Weight', true);
298
					}
299
					if ( !isset(dicomFile.getDicomTag('00080031'))  && !isset(dicomFile.getDicomTag('00080032')) ) {
300
						sr.setWarning('missingTag00101031', 'Missing tag: Series Time', true);
301
					}
302
					if ( !isset(dicomFile.getRadiopharmaceuticalTag('00181074')) ) {
303
						sr.setWarning('missingTag00181074', 'Missing tag: Radionuclide Total Dose', true);
304
					}
305
					if (!isset(dicomFile.getRadiopharmaceuticalTag('00181072')) && !isset(dicomFile.getRadiopharmaceuticalTag('00181078')) ) {
306
						sr.setWarning('missingTag00181072', 'Missing tag: Radiopharmaceutical Start Time', true);
307
					}
308
					if ( !isset(dicomFile.getRadiopharmaceuticalTag('00181075')) ) {
309
						sr.setWarning('missingTag00181075', 'Missing tag: Radionuclide Half Life', true);
310
					}
311
				}
312
			}
313
314
			// Check number of instances
315
			if(sr.getNbInstances() < this.config.minNbOfInstances) {
316
				sr.setWarning(`lessThan${this.config.minNbOfInstances}Instances`, `This serie contains less than ${this.config.minNbOfInstances} instances`, true, false);
317
			} else {
318
				delete sr.warnings[`lessThan${this.config.minNbOfInstances}Instances`];
319
			}
320
321
			if (sr.hasWarnings()) {
322
				st.setStatusSerie(sr, 'rejected');
323
				st.dequeueSerie(sr);
324
				st.setWarning('serie' + sr.seriesNumber, 'Invalid serie: #' + sr.seriesNumber + '.', false, false);
325
			} else {
326
				st.setStatusSerie(sr, 'valid');
327
				delete st.warnings['serie' + sr.seriesNumber];
328
			}
329
		}
330
	}
331
332
	findExpectedVisit(st) {
333
		let thisP = st.getPatientName();
334
335
		if (thisP.givenName === undefined) {
336
			return undefined;
337
		}
338
		if (thisP.familyName === undefined) {
339
			return undefined;
340
		}
341
342
		thisP.birthDate = st.getPatientBirthDate();
343
		thisP.sex = st.patientSex;
344
345
		if (thisP.birthDate === undefined || thisP.sex === undefined) {
346
			return undefined;
347
		}
348
349
		// Linear search through expected visits list
350
		for (let visit of this.expectedVisits) {
351
			if (visit.firstName.trim().toUpperCase().charAt(0) == thisP.givenName.trim().toUpperCase().charAt(0)
352
				&& visit.lastName.trim().toUpperCase().charAt(0) == thisP.familyName.trim().toUpperCase().charAt(0)
353
				&& visit.sex.trim().toUpperCase().charAt(0) == thisP.sex.trim().toUpperCase().charAt(0)
354
				&& Util.isProbablyEqualDates(visit.birthDate, thisP.birthDate)
0 ignored issues
show
Bug introduced by
The variable Util seems to be never declared. If this is a global, consider adding a /** global: Util */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
355
				) {
356
				return visit;
357
			}
358
		};
359
		return undefined;
360
361
	}
362
}